import java.util.Observable;

/**
 * Controls the flow of the game
 * @author Kat.Winter
 */
public class SudokuGame extends Observable {
	
	/**
	 * Size of the Sudoku grid, typically 9
	 */
	private int size;	
	
	/**
	 * Grid object to handle requests for game logic to be performed
	 */
	private Grid grid;
	
	/**
	 * Variable for whether or not there is an error in the game
	 */
	private boolean hasError = false;
	
	/**
	 * Variable for whether or not the given Row is a valid entry
	 */
	private boolean invalidRow = false;
	
	/**
	 * Variable for whether or not the given Column is a valid entry
	 */
	private boolean invalidCol = false;
	
	/**
	 * Variable for whether or not the given Square is a vaid entry
	 */
	private boolean invalidSquare = false;
	
	/**
	 * Variable for whether or not the player's guess falls within
	 * the valid range of numbers
	 */
	private boolean invalidGuessRange = false;
	
	/**
	 * Variable for whether or not the player's guess is a possible
	 * number or if it's already in use in this section, row, or column
	 */
	private boolean invalidPossibility = false;
	
	
	/**
	 * Constructor
	 * Creates a new Grid to handle the logic of the board.
	 * @param size  Size of this Sudoku board
	 * @param lvlChoice  Level of difficulty
	 */
	public SudokuGame(int size, String lvlChoice) {
		
		this.size = size;
		grid = new Grid(size, lvlChoice);
	}
	
	/**
	 * Initializes a new board and sets permanent values for a new game
	 */
	public void newGame() {

		grid.initBoard();
		grid.setPerms(true);
	}
	
	/**
	 * Resets the game board by having the Grid re-initialize the board
	 * and having the Grid reset the initial permanent values
	 */
	public void resetBoard() {
		
		grid.initBoard();
		grid.setPerms(false);
	}
	
	/**
	 * Getter for the value of the square at row r and column c on the grid
	 * @param r  Row of the square
	 * @param c  Column of the square
	 * @return  Value for this square of the grid
	 */
	public int getSquare(int r, int c) {
		
		return grid.squareValue(r, c);
	}
	
	/** 
	 * Checks the row and column input by the player and
	 * sets errors accordingly.
	 * @param row  Row input by the player
	 * @param col  Column input by the player
	 */
	public void validateInput(int row, int col) {
		
		resetError();

		if (grid.invalidInput(row)) {
			
			hasError = true;
			invalidRow = true;
		}
		
		if (grid.invalidInput(col)) {
			
			hasError = true;
			invalidCol = true;
		}
		
		if (!hasError) {

			if (grid.isSquarePermanent(row, col)) {
			
				hasError = true;
				invalidSquare = true;
			}
		}
	}
	
	/** 
	 * Checks the guess input by the player and
	 * sets errors accordingly.
	 * @param guess  The number guessed by the player
	 */
	public void validateGuess(int row, int col, int guess) {
		
		resetError();
		
		if (grid.invalidInput(guess)) {
			
			hasError = true;
			invalidGuessRange = true;
		}
		
		if (grid.invalidPossibility(row, col, guess)) {
			
			hasError = true;
			invalidPossibility = true;
		}
	}
	
	/**
	 * Getter for whether or not there is currently an error
	 * @return  true or false, is there is currently an error
	 */
	public boolean hasError() {
		
		return hasError;
	}
	
	/**
	 * Getter for the current error or errors. Plural errors are concatenated
	 * to form a multi-line error.
	 * @return  String representing the current error
	 */
	public String getError() {
		
		StringBuilder error = new StringBuilder();
		
		error.append("");
		
		if (invalidRow) {
			error.append("\nInvalid Row. Please enter a row from 1 to " + size + ".");
		}
		
		if (invalidCol) {
			error.append("\nInvalid Column. Please enter a column from 1 to " + size + ".");
		}
		
		if (invalidSquare) {
			error.append("\nThis square has a permanent value and cannot be changed.");
		}
		
		if (invalidGuessRange) {
			error.append("\nInvalid Input. Please enter a number between 0 and " + size + " (0 to reset the square).");
		}
		
		if (invalidPossibility) {
			error.append("\nInvalid Input. Please enter a number that is not already in this same row, column, or section (or 0 to reset the square).");
		}
		
		resetError();
		return error.toString();
	}
	
	/**
	 * Resets all errors to false. 
	 */
	public void resetError() {
		
		hasError = false;
		invalidRow = false;
		invalidCol = false;
		invalidSquare = false;
		invalidGuessRange = false;
		invalidPossibility = false;
	}
	
	/**
	 * Creates a String made up of the possible values for a square,
	 * which is gets from the Grid. The values are concatenated into
	 * a String and returned to the calling method.
	 * @param r  Row of the square whose values are to be checked
	 * @param c  Column of the square whose values are to be checked
	 * @return  An String consisting of all possible values for this square
	 */
	public String getPossible(int r, int c) {
		
		if (grid.isSquarePermanent(r, c)){
			return "This square has a fixed value and cannot be changed";
		}
		
		else {
			StringBuilder possibilities = new StringBuilder();
		
			possibilities.append("\nAvailable values: ");
		
			for (Integer i: grid.getPossibilities(r, c)) {
			
				possibilities.append(i);
				possibilities.append(" ");
			}
		
			return possibilities.toString();
		}
	}
	
	/**
	 * Checks to see if the game has been won.
	 * @return  True if the game has been won, False if the game is not yet won
	 */
	public boolean isGameWon() {
		
		if (grid.hasOpening()) {
			
			return false;
		}
		
		else {
			
			return true;
		}
	}
	
	/**
	 * Assigns a number representing the player's guess to the given square
	 * and then updates its observers so the UI can refresh to show
	 * the result with the new number in this square
	 * @param r  Row of the square in question
	 * @param c  Column of the square in question
	 * @param guess  Number that will be assigned to this square's value
	 */
	public void processGuess(int r, int c, int guess) {
		
		grid.assignValue(r, c, guess);
		
		setChanged();
		notifyObservers();
	}
}
